package org.ala.tiktools.sharddb.tools;

import org.ala.tiktools.sharddb.exception.ShardDBRuntimeException;

/**
 * 雪花id生成器
 * <p>  [0 | 41位时间戳 | 10位机器码 | 12位自增量]
 *
 * @author ala
 * @date 2025-02-26 13:11
 */
public class SnowIdGenerator {
    /**
     *  时间戳长度
     */
    public final static int LENGTH_TIMESTAMP = 41;
    /**
     *  默认机器码长度
     */
    public final static int LENGTH_PART_ID = 10;
    /**
     *  默认子增量长度
     */
    public final static int LENGTH_INCREMENT = 12;
    /**
     *  各种长度自定义
     */
    protected int lengthTimestamp = LENGTH_TIMESTAMP,
                    lengthPartId = LENGTH_PART_ID,
                    lengthIncrement = LENGTH_INCREMENT;
    /**
     *  当前机器码
     */
    protected int crtPartId;
    /**
     *  当前自增量
     */
    protected volatile int crtIncrement;
    /**
     *  上次生成id时间戳
     */
    protected volatile long lastTimestamp;
    /**
     *  是否允许篡改本地时间戳
     */
    protected boolean allowModifyLocalTimestamp;
    /**
     *  子增量满了的掩码
     */
    protected int maskFull;
    /**
     *  机器id掩码
     */
    protected int maskPartId;


    public static SnowIdGenerator build(int partId) {
        return SnowIdGenerator.build(partId, true);
    }
    public static SnowIdGenerator build(int partId, int lengthPartId, int lengthIncrement) {
        return SnowIdGenerator.build(LENGTH_TIMESTAMP, lengthPartId, lengthIncrement, partId, true);
    }
    public static SnowIdGenerator build(int partId, boolean allowModifyLocalTimestamp) {
        return SnowIdGenerator.build(LENGTH_TIMESTAMP, LENGTH_PART_ID, LENGTH_INCREMENT, partId, allowModifyLocalTimestamp);
    }
    public static SnowIdGenerator build(int lengthTimestamp, int lengthPartId, int lengthIncrement, int partId, boolean allowModifyLocalTimestamp) {
        SnowIdGenerator g = new SnowIdGenerator();
        g.lengthIncrement = lengthIncrement;
        g.lengthTimestamp = lengthTimestamp;
        g.lengthPartId = lengthPartId;
        g.maskFull = (1<<lengthIncrement) - 1;
        g.crtIncrement = 0;
        g.crtPartId = partId & ((1<<lengthPartId) - 1);
        g.maskPartId = g.crtPartId << lengthIncrement;
        g.allowModifyLocalTimestamp = allowModifyLocalTimestamp;
        return g;
    }
    /**
     *  生成id
     *  @return [id，生成id的时间戳，生成id的机器码，生成id的自增码]
     */
    public synchronized Id id() {
        long now = System.currentTimeMillis();
        if (now < lastTimestamp && !allowModifyLocalTimestamp) {
            throw new ShardDBRuntimeException("本地时间戳被篡改，无法生成id");
        }
        while (crtIncrement == maskFull && now == System.currentTimeMillis()) {}
        now = System.currentTimeMillis();
        if (now > lastTimestamp) {
            crtIncrement = 0;
            lastTimestamp = now;
        } else {
            crtIncrement++;
        }
        return Id.build((now << (lengthPartId + lengthIncrement)) | maskPartId | crtIncrement, crtPartId, now, crtIncrement);
    }
    /**
     *  取当初生成id的时间戳
     */
    public long timestamp(long id) {
        return id >> (lengthIncrement + lengthPartId);
    }


    /**
     *  id信息
     */
    public static class Id {
        /**
         *  生成的id
         */
        protected long id;
        /**
         *  生成id的机器码
         */
        protected int partId;
        /**
         *  生成id的时间戳
         */
        protected long timestamp;
        /**
         *  生成id的时间戳相对自增量
         */
        protected int increment;

        public static Id build(long id, int partId, long timestamp, int increment) {
            Id i = new Id();
            i.id = id;
            i.partId = partId;
            i.timestamp = timestamp;
            i.increment = increment;
            return i;
        }

        public long getId() {
            return id;
        }

        public void setId(long id) {
            this.id = id;
        }

        public int getPartId() {
            return partId;
        }

        public void setPartId(int partId) {
            this.partId = partId;
        }

        public long getTimestamp() {
            return timestamp;
        }

        public void setTimestamp(long timestamp) {
            this.timestamp = timestamp;
        }

        public int getIncrement() {
            return increment;
        }

        public void setIncrement(int increment) {
            this.increment = increment;
        }
    }
}
